﻿using System.Linq.Expressions;
using System.Reflection;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DynamicLinq;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 公共数据处理服务
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TKey">键类型</typeparam>
public class DataService<TDbContext, TEntitySet, TKey> :
    ServiceBase<TDbContext, TKey>,
    IDataService<TDbContext, TEntitySet, TKey>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<TKey>
    where TKey : IConvertible
{
    protected readonly Type _type;
    protected readonly TypeAdapterConfig _adapterConfig;
    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="context">数据库上下文</param>
    /// <param name="httpContextAccessor">Http上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="appSetting">配置</param>
    public DataService(
        TDbContext context,
        ILogger<DataService<TDbContext, TEntitySet, TKey>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting)
    {
        _type = typeof(TEntitySet);
        _adapterConfig = GetAdapterConfig();
        _enableCache = _enableCache && _type.HasAttribute<CacheableAttribute>();
    }

    #region 数据查询方法
    /// <summary>
    /// 统一数据源查询入口, 可缓存数据源, 仅返回当前类型引用
    /// </summary>
    /// <returns></returns>
    public virtual IQueryable<TEntitySet> GetQueryable() => GetQueryable<TEntitySet>();
    /// <summary>
    /// 非注入类型 TEntity 统一数据源查询入口, 可缓存数据源, 可返回泛型类型指定的任意类型引用
    /// </summary>
    /// <param name="predicate">过滤条件</param>
    /// <returns></returns>
    public virtual IQueryable<TEntitySet> GetQueryable(Expression<Func<TEntitySet, bool>> predicate) => GetQueryable<TEntitySet>(predicate);
    /// <summary>
    /// 根据 id 获取对象
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual async Task<TEntitySet?> GetAsync(TKey id) => await GetAsync<TEntitySet>(id);
    /// <summary>
    /// 从 QueryOptions 和上下文自动获取查询表达式的内容并执行查询返回分页的结果
    /// 暂不支持 orderby, select 和 expand 表达式
    /// </summary>
    /// <returns></returns>
    public virtual async Task<PagedResult<TEntitySet>> GetPagedResultAsync() => await GetPagedResultAsync<TEntitySet>();
    #endregion

    #region 数据操作方法
    /// <summary>
    /// 获取在更新复制属性值时需要忽略的字段配置
    /// </summary>
    /// <returns></returns>
    public virtual TypeAdapterConfig GetAdapterConfig() => GetAdapterConfig<TEntitySet>();
    /// <summary>
    /// 新增阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public virtual async Task UniqueAsync(TEntitySet entitySet) => await base.UniqueAsync(entitySet);
    /// <summary>
    /// 批量新增或修改数据对象模型到当前上下文, 但不提交
    /// </summary>
    /// <param name="entitySets">业务数据</param>
    /// <returns></returns>
    public virtual async Task AddOrUpdateAsync(params TEntitySet[] entitySets) => await AddOrUpdateAsync<TEntitySet>(entitySets);
    /// <summary>
    /// 单个数据对象的新增, 自动验证重复
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public virtual async Task<TEntitySet> AddAsync(TEntitySet entitySet) => await base.AddAsync(entitySet);
    /// <summary>
    /// 单个数据对象的新增, 自动验证重复
    /// 如果传入的业务数据类型是一个 IViewModel<TKey>, 则将业务数据当成当前实体类型的 ViewModel, 进行字段隐射, 并添加到数据库上下文, 此时只能返回处理后的 ViewModel
    /// 否则可接受任意来自 IEntitySet<TKey> 的实体对象类型
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    /// <returns></returns>
    public override async Task<TModel> AddAsync<TModel>(TModel model)
    {
        if (model is IViewModel<TKey> viewModel)
        {
            await UniqueViewModelAsync(viewModel);
            var entitySet = viewModel.Adapt<TEntitySet>(GetAdapterConfig<TEntitySet>());
            Create(entitySet);
            Update(entitySet);
            await _context.AddAsync(entitySet);

            if (AutoSaveChanges)
            {
                await SaveChangesAsync();
            }

            return model;
        }
        else
        {
            return await base.AddAsync(model);
        }
    }
    /// <summary>
    /// 更新阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作, 自动验证重复
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public virtual async Task<TEntitySet> UpdateAsync(TEntitySet entitySet) => await base.UpdateAsync(entitySet);
    /// <summary>
    /// 更新阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作, 自动验证重复
    /// 如果传入的业务数据类型是一个 IViewModel<TKey>, 则将业务数据当成当前实体类型的 ViewModel, 进行字段隐射, 并更新到数据库上下文, 此时只能返回处理后的 ViewModel
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    /// <returns></returns>
    public override async Task<TModel> UpdateAsync<TModel>(TModel model)
    {
        if (model is IViewModel<TKey> viewModel)
        {
            var entitySet = await GetAsync<TEntitySet>(viewModel.Id);
            if (entitySet == null)
            {
                throw new Exception($"{ typeof(TEntitySet).GetDisplayName()} 中当前记录不存在!");
            }

            await UniqueViewModelAsync(viewModel);
            viewModel.Adapt(entitySet, GetAdapterConfig<TEntitySet>());
            Update(entitySet);
            _context.Update(entitySet);

            if (AutoSaveChanges)
            {
                await SaveChangesAsync();
            }

            return model;
        }
        else
        {
            return await base.UpdateAsync(model);
        }
    }
    /// <summary>
    /// 删除记录
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <returns></returns>
    public virtual async Task DeleteAsync(TKey id) => await DeleteAsync<TEntitySet>(id);
    #endregion

    #region 内部成员方法
    /// <summary>
    /// 新增或修改阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作
    /// </summary>
    /// <typeparam name="TViewModel">业务数据的 ViewModel 数据类型</typeparam>
    /// <param name="viewModel">业务数据的 ViewModel</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    protected virtual async Task UniqueViewModelAsync<TViewModel>(TViewModel viewModel) where TViewModel : class, IViewModel<TKey>
    {
        var propertyInfos = _type.GetProperties().Where(x => x.HasAttribute<UniqueAttribute>());
        if (propertyInfos.IsNotNullOrEmpty())
        {
            var index = 0;
            var keys = new List<string>();
            var values = new List<object>();
            var messages = new List<string>();
            if (viewModel.Id != null && !viewModel.Id.Equals(default))
            {
                keys.Add($"{nameof(viewModel.Id)} != @{index++}");
                values.Add($"{viewModel.Id}");
            }

            foreach (var propertyInfo in propertyInfos)
            {
                var value = propertyInfo.GetValue(viewModel);
                keys.Add($"{propertyInfo.Name} == @{index++}");
                values.Add($"{value}");
                messages.Add($"<{propertyInfo.GetDisplayName()}> = {value}");
            }

            var predicate = string.Join(" and ", keys);
            var exist = await GetQueryable<TEntitySet>().AnyAsync(predicate, values.ToArray());
            if (exist)
            {
                throw new Exception($"{_type.GetDisplayName()} 中 {messages.ToString<string>()} 的记录已经存在!");
            }
        }
    }
    /// <summary>
    /// 获取业务数据的集合成员, 成员是附加信息, 附件及特定类型的附属集合
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <typeparam name="TMember">附加信息, 附件或附属集合的类型</typeparam>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    protected virtual IEnumerable<TMember>? GetCollectionMembers<TMember>(TEntitySet entitySet) where TMember : class, IEntitySet<TKey>
    {
        var propertyInfo = typeof(TEntitySet).GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.PropertyType.IsFromType<IEnumerable<TMember>>());
        if (propertyInfo != null && propertyInfo.GetValue(entitySet) is IEnumerable<TMember> members)
        {
            return members;
        }

        return null;
    }
    #endregion
}

/// <summary>
/// 公共数据处理服务
/// 字符串作为键的默认实现
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
public class DataService<TDbContext, TEntitySet> :
    DataService<TDbContext, TEntitySet, string>,
    IDataService<TDbContext, TEntitySet>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<string>
{
    public DataService(
        TDbContext context,
        ILogger<DataService<TDbContext, TEntitySet, string>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting) { }
}